package com.astrodroid.serial;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Arrays;

import com.astrodroid.serial.SerialPort;

/**
 * Serial Device Class
 */
public class SerialDevice {
	private static final String TAG = "SerialDevice";

	private SerialPort mSerialPort = null;
    private OutputStream mOutputStream = null;
    private InputStream mInputStream = null;
    private SerialRecvThread mRecvThread = null;
    private SerialSendThread mSendThread = null;
	private volatile boolean mStopRecv = false;

	private OnSerialRecvListener mOnSerialRecvListener = null;

	private static final SerialDevice mSerialDevice = new SerialDevice();	// Singleton Mode

    private byte[] recvbuf = new byte[256];
    private int recvidx = 0;

	/**
	 * Interface definition for a callback to be invoked when serial data received.
	 */
	public interface OnSerialRecvListener {
		/**
		 * Called when serial data received.
		 *
		 * @param buffer
		 * @param size
		 */
		void onSerialRecv(final byte[] buffer, final int size);
	}

	/**
	 * Register a callback to be invoked when serial data received.
	 *
	 * @param l The callback that will run
	 */
    public void setOnSerialRecvListener(OnSerialRecvListener l) {
		mOnSerialRecvListener = l;
    }

    /**
     * Private Constructor
     */
    private SerialDevice() {}

    /**
     * Static method return the sigleton instance.
	 *
	 * @return SerialDevice
     */
	public static SerialDevice getSerialDevice() {
		return mSerialDevice;
	}


    /**
     * send the data to other device via rs485 -- downlink
	 *
     * @param data byte[]
     * @throws IOException
     */
    public synchronized void send(byte[] data) throws IOException {
		String str = new String(data);
        if (!str.isEmpty()) {
			System.out.println(TAG + " send: " + bytesToHexString(data, data.length));		// dump byte[]

            mOutputStream.write(data);
            mOutputStream.flush();
        }
    }

    /**
     * send the string to other device via rs485 -- downlink
	 *
     * @param data String
     * @throws IOException
     */
    public void send(String data) throws IOException {
        if (data != null) {
            send(data.getBytes());
        }
    }

	/**
	 * receiving from other device via rs485 -- uplink
	 */
    byte[] buffer  = new byte[1024];
    private class SerialRecvThread extends Thread {
        @Override
        public void run() {
            super.run();
            while(!isInterrupted()) {
				int timeout = 100;	// ms
                int size = 0;
				try {
					if (mInputStream == null)
						return;

					size = mInputStream.read(buffer);
                    if (size > 0) {
                        if (mOnSerialRecvListener != null)
                            mOnSerialRecvListener.onSerialRecv(buffer, size);

						// data process...
						for (int x=0; x<size; x++) {
	                    	recvbuf[recvidx] = buffer[x];
	                    	recvidx++;
	                	}
						if ((buffer[size-1] == 0x0A) || (recvidx == 255)) {
	                    	String hexstr = bytesToHexString(recvbuf, recvidx);
							System.out.println(TAG + " recv: " + hexstr);

                    		recvidx = 0;
						}
                    }
				} catch (IOException e) {
                    e.printStackTrace();
                    return;
                }
            }
        }
    }

    private class SerialSendThread extends Thread {
        @Override
        public void run() {
            super.run();
            while(!isInterrupted()) {
				try {

					byte[] tmp = {0x01, 0x03, 0x00, 0x20, 0x00, 0x0D, (byte) 0x85, (byte) 0xC5};
					send(tmp);

					Thread.sleep(1000);		// 1s
				} catch (Exception e) {
                    e.printStackTrace();
                    return;
                }
            }
        }
    }

	/**
	 * open serial device
	 */
	public void open() {
		if (mSerialPort == null) {
			String devpath = "/dev/ttyUSB0";
			int baudrate = 19200;

			try {
				mSerialPort = new SerialPort(new File(devpath), baudrate, 0);
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		if (mInputStream == null)
			mInputStream = mSerialPort.getInputStream();
		if (mOutputStream == null)
			mOutputStream = mSerialPort.getOutputStream();


		/* Create a receiving thread */
		mRecvThread = new SerialRecvThread();
		mRecvThread.start();
	}

    /**
     * close serial device
     */
    public void close() {
        if (mInputStream != null){
            try {
                mInputStream.close();
				mInputStream = null;
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        if (mOutputStream != null){
            try {
                mOutputStream.close();
				mOutputStream = null;
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

		if (mSerialPort != null) {
			mSerialPort.close();
			mSerialPort = null;
		}
    }

	/**
	 * convert byte[] to String (of Hexadecimal)
	 *
	 * @param b byte[]
	 * @param length the number of bytes in byte[]
	 * @return String
	 */
	private static String bytesToHexString(byte[] b, int length) {
		StringBuilder strbuilder = new StringBuilder("");
		if ((b == null) || (length == 0)) {
			return null;
		}

		for (int i=0; i<length; i++) {
			strbuilder.append("0x");

			String str = Integer.toHexString(b[i] & 0xff);
			if (str.length() < 2)
				strbuilder.append(0);

			strbuilder.append(str.toUpperCase());
			strbuilder.append(' ');
		}

		return strbuilder.toString();
	}

	private void todo() {
		/* Create a sending thread */
		mSendThread = new SerialSendThread();
		mSendThread.start();
	}


	public static void main(String[] args) {
		mSerialDevice.open();
		mSerialDevice.todo();
	}
}
